<?php
/**
 * MineAdmin is committed to providing solutions for quickly building web applications
 * Please view the LICENSE file that was distributed with this source code,
 * For the full copyright and license information.
 * Thank you very much for using MineAdmin.
 *
 * @Author X.Mo<root@imoi.cn>
 * @Link   https://gitee.com/xmo/MineAdmin
 */

declare(strict_types=1);

namespace Newbie\Crontab;

use App\Admin\Service\Setting\SettingCrontabLogService;

class NewbieExecutor {
    // 命令任务
    public const COMMAND_CRONTAB = '1';
    // 类任务
    public const CLASS_CRONTAB = '2';
    // URL任务
    public const URL_CRONTAB = '3';
    // EVAL 任务
    public const EVAL_CRONTAB = '4';
    //shell 任务
    public const SHELL_CRONTAB = '5';

    /**
     * @var SettingCrontabLogService
     */
    protected SettingCrontabLogService $settingCrontabLogService;

    /**
     * 调试模式
     * @var bool
     */
    public bool $debug = false;

    public function __construct () {
        $this->settingCrontabLogService = container()->get(SettingCrontabLogService::class);
    }

    public function execute (NewbieCrontab $crontab): bool {
        $callback  = $this->getCallback($crontab);
        $startTime = microtime(true);
        $result    = $callback($crontab);
        $endTime   = microtime(true);
        $this->settingCrontabLogService->save([
            'crontab_id'     => $crontab->getCrontabId(),
            'name'           => $crontab->getName(),
            'target'         => $crontab->getTarget(),
            'parameter'      => $crontab->getParameter() ?: '',
            'exception_info' => $result['exception'],
            'status'         => $result['code'],
            'running_time'   => round($endTime - $startTime, 6),
            'created_at'     => date('Y-m-d H:i:s')
        ]);
        $this->debug && $this->writeln('执行定时器任务#' . $crontab->getName() . ' ' . $crontab->getRule() . ' ' . $crontab->getTarget(), $result['result']);
        return $result['result'];
    }

    private function executeOne (NewbieCrontab $crontab): bool {
        $this->debug && $this->writeProcessLn("立即运行一次 " . $crontab->getName(), "OnlyOne");
        return $this->execute($crontab);
    }

    public function getCallback (NewbieCrontab $crontab) {
        switch ($crontab->getType()) {
            case self::COMMAND_CRONTAB:
                return function (NewbieCrontab $crontab) {
                    $result = [
                        'code'      => 1,
                        'exception' => '',
                        'result'    => true
                    ];
                    try {
                        $command             = $crontab->getTarget();
                        $result['exception'] = (string)shell_exec($command);;
                    } catch (\Throwable $e) {
                        $result['result']    = false;
                        $result['code']      = 0;
                        $result['exception'] = $e->getMessage();
                    }
                    return $result;
                };
                break;
            case self::CLASS_CRONTAB:
                return function (NewbieCrontab $crontab) {
                    $result     = [
                        'code'      => 1,
                        'exception' => '',
                        'result'    => true
                    ];
                    $class      = trim($crontab->getTarget());
                    $method     = 'execute';
                    $parameters = $crontab->getParameter() ?: null;
                    if ($class && class_exists($class) && method_exists($class, $method)) {
                        try {
                            $instance = container()->get($class);
                            if ($parameters && is_array($parameters)) {
                                $res = $instance->{$method}(...$parameters);
                            } else {
                                $res = $instance->{$method}();
                            }
                            $result['exception'] = json_encode($res);
                        } catch (\Throwable $throwable) {
                            $result['result']    = false;
                            $result['code']      = 0;
                            $result['exception'] = $throwable->getMessage();
                        }
                    }
                    return $result;
                };
                break;
            case self::URL_CRONTAB:
                return function (NewbieCrontab $crontab) {
                    $result = [
                        'code'      => 1,
                        'exception' => '',
                        'result'    => true
                    ];
                    $url    = trim($crontab->getTarget());
                    $client = new \GuzzleHttp\Client();
                    try {
                        $response            = $client->get($url);
                        $result['result']    = $response->getStatusCode() === 200;
                        $result['code']      = (int)$result['result'];
                        $result['exception'] = $response->getBody();
                    } catch (\Throwable $throwable) {
                        $result['result']    = false;
                        $result['code']      = 0;
                        $result['exception'] = $throwable->getMessage();
                    }
                    return $result;
                };
                break;
            case self::SHELL_CRONTAB:
                return function (NewbieCrontab $crontab) {
                    $result = [
                        'code'      => 1,
                        'exception' => '',
                        'result'    => true
                    ];
                    try {
                        $command             = $crontab->getTarget();
                        $result['exception'] = (string)shell_exec($command);;
                    } catch (\Throwable $throwable) {
                        $result['result']    = false;
                        $result['code']      = 0;
                        $result['exception'] = $throwable->getMessage();
                    }
                    return $result;
                };
                break;
            case self::EVAL_CRONTAB:
                return function (NewbieCrontab $crontab) {
                    $result = [
                        'code'      => 1,
                        'exception' => '',
                        'result'    => true
                    ];
                    try {
                        eval($crontab->getTarget());
                    } catch (\Throwable $throwable) {
                        $result['result']    = false;
                        $result['code']      = 0;
                        $result['exception'] = $throwable->getMessage();
                    }
                    return $result;
                };
                break;
        }
    }

    /**
     * 输出日志
     * @param string $msg
     * @param bool $isSuccess
     */
    private function writeln (string $msg, bool $isSuccess = false): void {
        echo '[' . date('Y-m-d H:i:s') . '] ' . $msg . ($isSuccess ? " [Ok] " : " [Fail] ") . PHP_EOL;
    }

    /**
     * 输出日志
     * @param string $msg
     * @param string $process
     */
    private function writeProcessLn (string $msg, string $process): void {
        echo '[' . date('Y-m-d H:i:s') . '] ' . $msg . (" [$process] ") . PHP_EOL;
    }
}